package networkLoader;

import network.Device;
import network.Network;
import network.Rule;
import partition.Partition;
import partition.SubnetPartition;
import verifier.util.BDDEngine;

import java.io.FileNotFoundException;
import java.io.UnsupportedEncodingException;
import java.util.*;

public class FabricNetwork {
    public static Network getNetwork(int npod) {
        Network n = new Network("FabricNetwork");
//        n.addDevice("default");
        for (int i = 0; i < npod; i++) {
            for (int j = 0; j < 48; j++) {
                Device device = n.addDevice("rsw-" + i + "-" + j);
//                device.addPort("rsw-" + i + "-" + j + ">h");
//                n.addLink("rsw-" + i + "-" + j, "default", "default", "default>" + "rsw-" + i + "-" + j);
                // add host (partition need)
                device.addPort(String.format("rsw-%d-%d>h-%d-%d", i, j, i, j));
//                Device host = n.addDevice(String.format("h-%d-%d", i, j));
//                n.addLink(String.format("rsw-%d-%d", i, j), String.format("rsw-%d-%d>h-%d-%d", i, j, i, j), String.format("h-%d-%d", i, j), String.format("h-%d-%d>rsw-%d-%d", i, j, i, j));
            }

            for (int j = 0; j < 4; j++) {
                Device device = n.addDevice("fsw-" + i + "-" + j);
//                n.addLink("fsw-" + i + "-" + j, "default", "default", "default>" + "fsw-" + i + "-" + j);
            }
        }

        for (int i = 0; i < 4; i++) {
            for (int j = 0; j < 48; j++) {
                Device device = n.addDevice("ssw-" + i + "-" + j);
//                n.addLink("ssw-" + i + "-" + j, "default", "default", "default>" + "ssw-" + i + "-" + j);
            }
        }

        // add links rsw-fsw
        for (int i = 0; i < npod; i++) {
            for (int j = 0; j < 48; j++) {
                String rswname = "rsw-" + i + "-" + j;
                for (int k = 0; k < 4; k++) {
                    String fswname = "fsw-" + i + "-" + k;
                    n.addLink(rswname, rswname + ">" + fswname, fswname, fswname + ">" + rswname);
                }
            }
        }

        // add links fsw-ssw
        for (int i = 0; i < 4; i++) {
            for (int j = 0; j < 48; j++) {
                String sswname = "ssw-" + i + "-" + j;
                for (int k = 0; k < npod; k++) {
                    String fswname = "fsw-" + k + "-" + i;
                    n.addLink(sswname, sswname + ">" + fswname, fswname, fswname + ">" + sswname);
                }
            }
        }
        boolean flag = false;
        // gen rules
        // first 8 bits is pod id, next 8 bit is index of rsw in pod
        for (int i = 0; i < npod; i++) {
            for (int j = 0; j < 48; j++) {
                String rswname = "rsw-" + i + "-" + j;
                Device device = n.getDevice(rswname);
                for (int k = 0; k < npod; k++) {
                    for (int l = 0; l < 48; l++) {
                        String dstrswname = "rsw-" + k + "-" + l;
                        if (dstrswname.equals(rswname)) {
                            long dstip = (k << 24) + ((l + 1) << 16);
                            Rule rule = new Rule(device, dstip, 16, device.getPort(rswname + ">h-" + k + "-" + l));
                            n.addInitialRule(rule);
                            device.addInitialRule(rule);
                            continue;
                        }
                        long dstip = (k << 24) + ((l + 1) << 16);
//                        String dstfswname = "fsw-" + i + "-" + 0;
                        String dstfswname = "fsw-" + i + "-" + (48 * k + l) % 4;
                        Rule rule = new Rule(device, dstip, 16, device.getPort(rswname + ">" + dstfswname));
                        if (flag) {
                            rule = new Rule(device, dstip, 16, device.getPort("default"));
                            flag = false;
                        }
                        n.addInitialRule(rule);
                        device.addInitialRule(rule);
                    }
                }
            }

            for (int j = 0; j < 4; j++) { // spine id
                String fswname = "fsw-" + i + "-" + j;
                Device device = n.getDevice(fswname);
                for (int k = 0; k < npod; k++) {
                    for (int l = 0; l < 48; l++) {
                        String dstrswname = "rsw-" + k + "-" + l;
                        long dstip = (k << 24) + ((l + 1) << 16);
                        if (k == i) { // same pod
                            Rule rule = new Rule(device, dstip, 16, device.getPort(fswname + ">" + dstrswname));
                            n.addInitialRule(rule);
                            device.addInitialRule(rule);
                        } else {
//                            String dstsswname = "ssw-" + j + "-" + 0;
                            String dstsswname = "ssw-" + j + "-" + (k * l) % 48;
                            Rule rule = new Rule(device, dstip, 16, device.getPort(fswname + ">" + dstsswname));
                            n.addInitialRule(rule);
                            device.addInitialRule(rule);
                        }
                    }
                }
            }
        }

        for (int i = 0; i < 4; i++) { // spine id
            for (int j = 0; j < 48; j++) {
                String sswname = "ssw-" + i + "-" + j;
                Device device = n.getDevice(sswname);
                for (int k = 0; k < npod; k++) {
                    for (int l = 0; l < 48; l++) {
                        long dstip = (k << 24) + ((l + 1) << 16);
                        String dstfswname = "fsw-" + k + "-" + i;
                        Rule rule = new Rule(device, dstip, 16, device.getPort(sswname + ">" + dstfswname));
                        n.addInitialRule(rule);
                        device.addInitialRule(rule);
                    }
                }
            }
        }

        return n;
    }

    public static HashMap<String, Partition> setupPartitions(Network n) {
        LinkedHashMap<String, Partition> nameToPartition = new LinkedHashMap<>();
        for (int i = 0; i < 112; i++) { // pod id
            LinkedList<Device> partition = new LinkedList<>();
//            partition.add(n.getDevice("default"));
            for (Device device : n.getAllDevices()) {
                if (device.getName().contains("rsw-" + i + "-") || device.getName().contains("fsw-" + i + "-")) {
                    partition.add(device);
                }
            }
            Network pt = n.getSubNetwork(partition);
            Partition partition1 = new Partition("pod-" + i, pt);
            nameToPartition.put("pod-" + i, partition1);
        }

        for (int i = 0; i < 4; i++) { // spine
            for (int j = 0; j < 48; j++) { // spine.ssw
                LinkedList<Device> partition = new LinkedList<>();
//                partition.add(n.getDevice("default"));
                String ssw = "ssw-" + i + "-" + j;
                partition.add(n.getDevice(ssw));
                Network pt = n.getSubNetwork(partition);
                Partition partition1 = new Partition("spine-" + i + "-" + j, pt);
                nameToPartition.put("spine-" + i + "-" + j, partition1);
            }
        }

        return nameToPartition;
    }

    public static HashMap<String, SubnetPartition> setupSubnetPartitions(Network n) {
        LinkedHashMap<String, SubnetPartition> nameToPartition = new LinkedHashMap<>();
        for (int i = 0; i < 112; i++) { // pod id
            LinkedList<Device> partition = new LinkedList<>();
//            partition.add(n.getDevice("default"));
            for (Device device : n.getAllDevices()) {
                if (device.getName().contains("rsw-" + i + "-") || device.getName().contains("fsw-" + i + "-")) {
                    partition.add(device);
                }
            }
            Network pt = n.getSubNetwork(partition);
            for (int iSubnet = 1; iSubnet < 2; iSubnet++) { // iSubnet is up to 112 in real cases， here we use pod2's prefix as subnet
                long ip = (iSubnet << 24);
                SubnetPartition partition1 = new SubnetPartition("pod-" + i + "-s-" + iSubnet, pt, ip, 8);
                nameToPartition.put("pod-" + i + "-s-" + iSubnet, partition1);
            }
        }

        for (int i = 0; i < 4; i++) { // spine
            for (int j = 0; j < 48; j++) { // spine.ssw
                LinkedList<Device> partition = new LinkedList<>();
//                partition.add(n.getDevice("default"));
                String ssw = "ssw-" + i + "-" + j;
                partition.add(n.getDevice(ssw));
                Network pt = n.getSubNetwork(partition);
                for (int iSubnet = 1; iSubnet < 2; iSubnet++) {
                    long ip = (iSubnet << 24);
                    SubnetPartition partition1 = new SubnetPartition("spine-" + i + "-" + j + "-s-" + iSubnet, pt, ip, 15);
                    nameToPartition.put("spine-" + i + "-" + j + "-s-" + iSubnet, partition1);
                }
            }
        }

        return nameToPartition;
    }

    public static void main(String[] args) throws FileNotFoundException, UnsupportedEncodingException {
        Network n = getNetwork(112);
        BDDEngine bddEngine = new BDDEngine(32);
        HashSet<Integer> bdds = new HashSet<>();
        HashSet<Rule> rules = new HashSet<>();
        System.out.println(n.getDevice("rsw-0-0").getInitialRules().size());
        for (Rule rule : n.getDevice("rsw-0-0").getInitialRules()) {
            if (rule.getOutPort().getPeerDevice() != null && rule.getOutPort().getPeerDevice().getName().equals("fsw-0-1")) {
                rules.add(rule);
                bdds.add(bddEngine.encodeIpv4(rule.getMatch(), 16));
            }
        }
        int r = bdds.stream().reduce(0, bddEngine::or);
        bddEngine.getBdd().printSet(r);
//            n.getDevice("rsw-0-0")
//                    .getInitialRules().stream()
//                    .filter(rule -> rule.getOutPort().getPeerDevice().getName().equals("fsw-0-0")).collect(Collectors.toSet())
//                    .forEach(r -> bdds.add(bddEngine.encodeIpv4(r.getMatch(), 16)));
        System.out.println(rules.size());

//        for (int i = 0; i < 4; i++) {
//            Network n = getNetwork(112);
//            HashMap<String, Partition> partitions = setupPartitions(n);
//
//            for (Map.Entry<String, Partition> entry : partitions.entrySet()) {
//                Partition partition = entry.getValue();
//                if (!entry.getKey().equals("pod-0") && !entry.getKey().equals("pod-1")) {
////            if (entry.getKey().startsWith("spine-")) {
//                    continue;
//                }
//
//                partition.test(1);
//                System.out.println(partition.getVerifier().predSize());
//            }
//            System.gc();
//        }

        System.exit(0);

    }
}
